<?php

namespace Xin\Process;

use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\InputStream;
use Symfony\Component\Process\Process;
use Xin\Capsule\WithConfig;
use Xin\Support\Str;

abstract class AppWork
{
	use WithConfig, InteractsWithDump;

	/**
	 * @var array
	 */
	protected $config;

	/**
	 * @param array $config
	 */
	public function __construct(array $config = [])
	{
		$this->config = $config;
	}

	/**
	 * 立即执行进程
	 * @param string|array $command
	 * @param callable|null $callback
	 * @param array $envs
	 * @param InputStream|null $input
	 * @param int|null $timeout
	 * @return Process
	 */
	public function processRun($command, callable $callback = null, array $envs = [], InputStream $input = null, int $timeout = null): Process
	{
		$process = $this->process($command, $envs, $input, $timeout);

		if (method_exists($process, 'starting')) {
			$process->starting(function () use ($process) {
				$this->dumpProcessInfo($process);
			});
		} else {
			$this->dumpProcessInfo($process);
		}

		if ($callback) {
			$process->run(self::processProcessingParsingHandler($process, $callback));
		} else {
			$process->run();
		}

		return $process;
	}

	/**
	 * 立即执行进程，如果失败，则抛出异常
	 * @param string|array $command
	 * @param callable|null $callback
	 * @param array $envs
	 * @param InputStream|null $input
	 * @param int|null $timeout
	 * @return Process
	 * @throws ProcessFailedException
	 */
	public function processRunOrFail($command, callable $callback = null, array $envs = [], InputStream $input = null, int $timeout = null)
	{
		$process = $this->process($command, $envs, $input, $timeout);

		if (method_exists($process, 'starting')) {
			$process->starting(function () use ($process) {
				$this->dumpProcessInfo($process);
			});
		} else {
			$this->dumpProcessInfo($process);
		}

		if ($callback) {
			$process->mustRun(self::processProcessingParsingHandler($process, $callback));
		} else {
			$process->mustRun();
		}

		return $process;
	}

	/**
	 * 打印进程信息
	 * @param Process $process
	 * @return void
	 */
	protected function dumpProcessInfo(Process $process)
	{
		$this->dumpInfo("Starting process shell commandline:", $this->dumpInfoText($process->getCommandLine()));
	}

	/**
	 * 实例化一个进程
	 * @param string|array $command
	 * @param array $envs
	 * @param mixed $input
	 * @param float|null $timeout
	 * @return Process
	 */
	public function process($command, array $envs = [], InputStream $input = null, ?float $timeout = null)
	{
		if (is_array($command)) {
			$command = implode(' ', $command);
		}

		// 初始化项目名称
		$projectName = $this->parseCommandAsProjectName($command);
		$projectName = $projectName ?: "default";

		// 项目方法名
		$projectMethod = Str::studly($projectName);

		// 获取指定项目的工作路径
		$workDirectoryMethod = "get{$projectMethod}WorkDirectory";
		if ($projectName == "default" || !method_exists($this, $workDirectoryMethod)) {
			$workDirectoryPath = $this->getWorkDirectory(true);
		} else {
			$workDirectoryPath = call_user_func([$this, $workDirectoryMethod], true);
		}

		// 获取指定项目的环境变量
		$processEnvsMethod = "get{$projectMethod}ProcessEnvs";
		if ($projectName == "default" || !method_exists($this, $processEnvsMethod)) {
			$envs = array_merge($this->getDefaultProcessEnvs(), $envs);
		} else {
			$envs = array_merge($this->{$processEnvsMethod}(), $envs);
		}

		// 获取指定项目的进程超时时间
		if ($timeout === null) {
			$processTimeoutMethod = "get{$projectMethod}ProcessTimeout";
			if ($projectName == "default" || !method_exists($this, $processTimeoutMethod)) {
				$timeout = $this->getDefaultProcessTimeout();
			} else {
				$timeout = $this->{$processTimeoutMethod}();
			}
		}

		// 构建实例
		$process = $this->makeProcessFromShellCommandline(
			$command,
			$workDirectoryPath,
			$envs,
			$input,
			$timeout
		);

		return $this->pretreatmentProcess($process, $projectName);
	}

	/**
	 * 构建实例
	 * @param array|string $command
	 * @param mixed $workDirectoryPath
	 * @param array $envs
	 * @param mixed $input
	 * @param float|null $timeout
	 * @return Process
	 */
	protected function makeProcessFromShellCommandline($command, string $workDirectoryPath, array $envs = [], InputStream $input = null, ?float $timeout = null)
	{
		return Process::fromShellCommandline(
			$command,
			$workDirectoryPath,
			$envs,
			$input,
			$timeout
		);
	}

	/**
	 * @inerhitDoc
	 */
	protected function pretreatmentProcess(Process $process, $projectName): Process
	{
		// 控制台120秒无输出，则认为失败，避免任务假死
		$process->setIdleTimeout(180);

		return $process;
	}

	/**
	 * 获取默认的进程的环境变量信息
	 * @return array
	 */
	protected function getDefaultProcessEnvs(): array
	{
		return [];
	}

	/**
	 * 获取默认的进程的超时时间
	 * @return int|null
	 */
	protected function getDefaultProcessTimeout(): ?int
	{
		return 7200;
	}

	/**
	 * @param mixed $command
	 * @return string
	 */
	protected function parseCommandAsProjectName(string $command)
	{
		return "default";
	}

	/**
	 * 获取工作目录
	 * @param bool $valid
	 * @return string
	 */
	abstract protected function getWorkDirectory(bool $valid = false);

	/**
	 * 处理进程执行
	 * @param Process $process
	 * @param callable $callback
	 * @return \Closure
	 */
	protected static function processProcessingParsingHandler(Process $process, callable $callback)
	{
		return function (string $type, string $data) use ($callback, $process) {
			$data = explode("\n", $data);
			foreach ($data as $item) {
				if (!empty($item)) {
					$callback($type, $item, $process);
				}
			}
			$process->clearOutput();
		};
	}
}
